﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
using GenericLibrary.Xml;
using GenericLibrary.Xml.Xsl;
using GenericLibrary.Wcf;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Reflection.Emit;

namespace GenericLibrary.Xml.Xsl {

    public interface IXslTransformCompileContext<T> where T : class, IXslTransformCompileContext<T>, new() {

        DirectoryInfo GetWorkingDirectory();
        XmlResolver GetResolver();
        XsltSettings GetSettings();
    }

    public interface IXslTransformExecuteContext<T> where T : class, IXslTransformExecuteContext<T>, new() {

        XsltArgumentList GetArgumentList();
    }

    class XslTransformCompiler<TContext> : MarshalByRefObject, IDisposable where TContext : class, IXslTransformCompileContext<TContext>, new() {

        private TContext _context;

        public XslTransformCompiler() {
            this._context = new TContext();
        }

        public bool Compile( Stream xsltStream, out string assemblyPath ) {
            try {
                using( xsltStream ) {
                    DirectoryInfo workingDirectory = this._context.GetWorkingDirectory();
                    string subDirName;
                    do {
                        subDirName = Path.GetFileNameWithoutExtension( Path.GetRandomFileName() );
                    } while( workingDirectory.GetDirectories( subDirName ).Length > 0 );
                    assemblyPath = workingDirectory.CreateSubdirectory( subDirName ).FullName + @"\xslt.dll";

                    AssemblyName an = new AssemblyName( "xslt" );
                    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly( an, AssemblyBuilderAccess.Save, Path.GetDirectoryName( assemblyPath ) );
                    ModuleBuilder mb = ab.DefineDynamicModule( "xslt", "xslt.dll", true );
                    TypeBuilder tb = mb.DefineType( "Xslt", TypeAttributes.Public | TypeAttributes.Class );

                    using( XmlReader reader = new XPathDocument( xsltStream ).CreateNavigator().ReadSubtree() ) {
                        foreach( CompilerError error in XslCompiledTransform.CompileToType( reader, this._context.GetSettings(), this._context.GetResolver(), false, tb, Path.GetDirectoryName( assemblyPath ) + @"\script.dll" ) ) {
                            return false;
                        }
                    }

                    ab.Save( "xslt.dll" );
                    return true;
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #region IDisposable Members

        public void Dispose() {
            try {
                if( this._context is IDisposable ) {
                    ( this._context as IDisposable ).Dispose();
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #endregion
    }

    class XslTransformExecutor<TContext> : MarshalByRefObject, IDisposable where TContext : class, IXslTransformExecuteContext<TContext>, new() {

        private TContext _context;

        public XslTransformExecutor() {
            this._context = new TContext();
        }

        public void Execute( string assemblyPath, Stream inputStream, Stream outputStream ) {
            try {
                using( inputStream ) {
                    Assembly assembly = Assembly.LoadFrom( assemblyPath );
                    Type type = assembly.GetType( "Xslt" );
                    XslCompiledTransform xslt = new XslCompiledTransform( false );
                    xslt.Load( type );
                    using( XmlReader reader = XmlReader.Create( inputStream ) ) {
                        xslt.Transform( reader, this._context.GetArgumentList(), outputStream );
                        reader.Close();
                    }
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #region IDisposable Members

        public void Dispose() {
            try {
                if( this._context is IDisposable ) {
                    ( this._context as IDisposable ).Dispose();
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #endregion
    }

    [ServiceContract( Namespace = "GenericLibrary.Xml.Xsl", Name = "XmlDataTransformService" )]
    public interface IXmlDataTransformService {

        [OperationContract]
        bool Prepare( Stream xslt );

        [OperationContract]
        Stream Transform( Stream input );
    }

    [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single )]
    public class XmlDataTransformService<TXslTransformCompileContext, TXslTransformExecuteContext> : IXmlDataTransformService, IDisposable
        where TXslTransformCompileContext : class, IXslTransformCompileContext<TXslTransformCompileContext>, new()
        where TXslTransformExecuteContext : class, IXslTransformExecuteContext<TXslTransformExecuteContext>, new() {

        private string _assemblyPath;

        private void DeleteAssemblyDirectory() {
            try {
                if( this._assemblyPath != null && this._assemblyPath.Length > 0 ) {
                    Directory.Delete( Path.GetDirectoryName( this._assemblyPath ), true );
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #region IXmlDataTransformService Members

        public bool Prepare( Stream xsltStream ) {
            try {
                using( xsltStream ) {
                    this.DeleteAssemblyDirectory();

                    AppDomainSetup ads = new AppDomainSetup();
                    ads.ApplicationBase = System.Environment.CurrentDirectory;
                    ads.DisallowBindingRedirects = false;
                    ads.DisallowCodeDownload = true;
                    ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
                    AppDomain workingDomain = AppDomain.CreateDomain( "WorkingDomain", null, ads );

                    XslTransformCompiler<TXslTransformCompileContext> compiler = workingDomain.CreateInstanceAndUnwrap( typeof( XslTransformCompiler<TXslTransformCompileContext> ).Assembly.FullName, typeof( XslTransformCompiler<TXslTransformCompileContext> ).FullName ) as XslTransformCompiler<TXslTransformCompileContext>;
                    if( compiler.Compile( xsltStream, out this._assemblyPath ) ) {
                        AppDomain.Unload( workingDomain );
                        return true;
                    } else {
                        AppDomain.Unload( workingDomain );
                        this.DeleteAssemblyDirectory();
                        return false;
                    }
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        public Stream Transform( Stream inputStream ) {
            try {
                using( inputStream ) {
                    AppDomainSetup ads = new AppDomainSetup();
                    ads.ApplicationBase = System.Environment.CurrentDirectory;
                    ads.DisallowBindingRedirects = false;
                    ads.DisallowCodeDownload = true;
                    ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
                    AppDomain workingDomain = AppDomain.CreateDomain( "WorkingDomain", null, ads );
                    XslTransformExecutor<TXslTransformExecuteContext> executor = workingDomain.CreateInstanceAndUnwrap( typeof( XslTransformExecutor<TXslTransformExecuteContext> ).Assembly.FullName, typeof( XslTransformExecutor<TXslTransformExecuteContext> ).FullName ) as XslTransformExecutor<TXslTransformExecuteContext>;

                    Stream outputStream = new MemoryStream();
                    executor.Execute( this._assemblyPath, inputStream, outputStream );
                    outputStream.Position = 0;
                    AppDomain.Unload( workingDomain );

                    return outputStream;
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
            try {
                this.DeleteAssemblyDirectory();
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        #endregion

        /*
        svcutil.exe /language:cs /out:XmlDataTransformServiceClient.cs /config:app.config http://localhost:8000/Services
        */

        public static ServiceHost CreateHost( Uri uri ) {
            try {
                ServiceHost host = new ServiceHost( typeof( XmlDataTransformService<TXslTransformCompileContext, TXslTransformExecuteContext> ), uri );

                ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
                smb.HttpGetEnabled = true;
                smb.HttpGetUrl = uri;
                smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
                host.Description.Behaviors.Add( smb );
                host.AddServiceEndpoint( ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "Mex" );

                WSHttpBinding basicBinding = new WSHttpBinding();
                basicBinding.ReceiveTimeout = new TimeSpan( 0, 5, 0 );
                basicBinding.SendTimeout = new TimeSpan( 0, 5, 0 );
                basicBinding.MaxReceivedMessageSize = int.MaxValue;//要进行数据压缩！

                host.AddServiceEndpoint( typeof( IXmlDataTransformService ), basicBinding, "XmlDataTransformService" );

                CustomBinding customBinding = GZipMessageEncodingBindingElement.PlugIn( basicBinding );
                customBinding.ReceiveTimeout = new TimeSpan( 0, 5, 0 );
                customBinding.SendTimeout = new TimeSpan( 0, 5, 0 );

                host.AddServiceEndpoint( typeof( IXmlDataTransformService ), customBinding, "XmlDataTransformServiceWithMessageZip" );

                return host;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }
    }

    public class DefaultXslTransformCompileContext : IXslTransformCompileContext<DefaultXslTransformCompileContext> {

        public DirectoryInfo GetWorkingDirectory() {
            return new DirectoryInfo( AppDomain.CurrentDomain.BaseDirectory );
        }

        public XmlResolver GetResolver() {
            return null;
        }

        public XsltSettings GetSettings() {
            return new XsltSettings( false, true );
        }

    }

    public class DefaultXslTransformExecuteContext : IXslTransformExecuteContext<DefaultXslTransformExecuteContext> {

        public XsltArgumentList GetArgumentList() {
            try {
                XsltArgumentList argumentList = new XsltArgumentList();
                XsltExtension extension = new XsltExtension();
                argumentList.AddExtensionObject( extension.NamespaceUri, extension );
                return argumentList;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

    }
}
